- Special Edition Using Visual Basic Script -

CHAPTER 20 - VBScript Forms, Controls, and Managing Transactions

by Ron Schwarz


In this chapter

The new HTML Layout Page features, combined with VBScript techniques, make it possible to do the hitherto unthinkableùcreate a web page that behaves like a real program, rather than a "souped-up" document. Although there's a fair amount of work involved, and several layers of interlocked logic are required, it's definitely something you should consider using if you want your work to appear as professional as possible.

The example in this chapter demonstrates everything you'll need to know to accomplish this on the client end. You'll also need to provide something on the server end to supply the data you're requesting. That's covered in Chapter 21, "Accessing Data."

The discussion of the project in this chapter assumes that you've loaded the project from the CD into your copy of the ActiveX Control Pad. To do this, right-click the icon for the Validation.htm file. When the pop-up menu appears, select the entry to load it into the ActiveX Control Pad.

Creating a Web Application

The example presented in this chapter uses the sample Publishers database supplied with Microsoft SQL Server 6.5. The principles involved in sending, receiving, and presenting data can be used with whatever type of system you're using, as long as it can be configured to return its data in the form of an HTML page based on a template you design.

The steps involved in creating an application such as this example are these:

1.Define the nature of the transactions (such as what data sent, and what data received).

2.Create the database on the server.

3.Design the form with the ActiveX Control Pad.

4.Write supporting code for data transfer and control handling.

Hiding Data in a Frame

Due to the reluctance of HTML to support true two-way interactivity, retrieving data into the current screen requires some fancy footwork. The approach taken in this chapter consists of a bit of apparent deception; we "trick" Internet Explorer into thinking it's receiving a new page by using a near-hidden frame as a type of data buffer. The reason it's "near" hidden is because it's not possible to completely hide a frame; however, by sizing it to one pixel by one pixel, it will be a light gray spot on a gray background. Even if you know what you're looking for, and where on the screen to look, you'll have a hard time finding it.

By hiding incoming data in this frame, you make it available to your script, and you can extract, manipulate, and display it any way you choose. You are no longer at the mercy of HTML.

Handling Queries

After you've created your database, you'll need to have code on the server to respond to queries and create HTML content. Two queries are used in this example. One creates a list of Publishers, and one returns a list of books. Each query consists of two files: an .IDC file, which contains the actual SQL statement, and an .HTX file, which contains an HTML template. This example uses the Internet Data Control (IDC), which is described in Chapter 21, "Accessing Data."

Listing 20.1 shows the SQL statement used to create the query that returns a list of all publishers in the database. It's not necessary for you to understand SQL in order to create VBScript to create applications like the example in this chapter, if you have someone available to handle that end of the job. This is feasible because the client and server ends of the project are completely separate. The one line you need to pay attention to defines the .HTX template file that the query uses for output. In this case, it's pub.htx, as shown in the third line.

Listing 20.1ùpub.idc Publishers SQL Statement File

Datasource: Publishers
Username: sa
Template: pub.htx
SQLStatement:
+SELECT Publishers.Pub_ID,
+Publishers.Pub_Name,
+HighPrice = Max(price),
+LowPrice = Min(price)
+FROM Publishers,
+Titles
+WHERE Publishers.pub_id =
+Titles.Pub_ID
+GROUP BY Publishers.pub_id, Pub_Name

Listing 20.2 appears at first glance to be fairly standard HTML, with the addition of an ActiveX ListBox control (lstPublishers), contained in an <OBJECT> tag, and a short script at the end of the file. One thing you should notice is the inclusion of a series of placeholders in the script, such as "<%Pub_Name%>". These contain instructions for the Internet Data Control, telling it what data to insert. The inserted data will replace the placeholders in the HTML file that is sent to your buffer frame. Everything between the <%begindetail%> and <%enddetail%> placeholder will be duplicated one time for each record in the result set created by the query.

Listing 20.2ùpub.htx Publishers HTML Template File

<HTML>
<HEAD><TITLE>Publishers List</TITLE></HEAD>
<BODY>
<BODY BGCOLOR="FFFFFF">
<OBJECT ID="lstPublishers" WIDTH=90 HEIGHT=90
CLASSID="CLSID:8BD21D20-EC42-11CE-9E0D-00AA006002F3">
<PARAM NAME="ScrollBars" VALUE="3">
<PARAM NAME="DisplayStyle" VALUE="2">
<PARAM NAME="Size" VALUE="5151;811">
<PARAM NAME="MatchEntry" VALUE="0">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<Script Language="VBScript">
<%begindetail%>
lstPublishers.AddItem "<%Pub_Name%>" & ";" & _
"<%Pub_ID%>" & ";" & _
"<%HighPrice%>" & ";" & _
"<%LowPrice%>"
<%enddetail%>
Top.TriggerPub
</script>
</BODY>
</HTML>

Right after the result definition, you'll see a VBScript statement of Top.TriggerPub. This is a call to a procedure in the application that tells it that the data is loaded into the buffer, and it's OK to proceed to display it in the form.

This file is typical of those you'll encounter when using Microsoft SQL Server 6.5 and the IDC. If you're using different server-end software, your queries will have to be handled accordingly. The server-end details are irrelevant to your work with VBScript and the ActiveX Control Pad, as long as the server is able to accept a query and return an HTML page.

Listing 20.3 contains the Titles query, which returns a list of books via the Titles.htx template. The same rules described for listing 20.1 also apply here.

Listing 20.3ùtitles.idc Titles SQL Statement File

Datasource: Publishers
Username: sa
Template: Titles.htx
SQLStatement:
+SELECT Distinct Titles.*,
+Publishers.*,
+titleAuthor.*,
+Authors.*
+FROM Titles Join Publishers
+on titles.Pub_ID = publishers.Pub_ID
+Left Join titleauthor on
+Titles.title_id = titleauthor.title_id
+Join Authors on titleauthor.au_id = authors.au_id
+WHERE Titles.Price >= Convert(money, '%LowPrice%')
+AND Titles.Price <= Convert(Money, '%HighPrice%')
+AND Publishers.Pub_id ='%PubID%'

Listing 20.4 is similar in concept to the file in listing 20.2, and the same rules apply. As in that file, the script ends with a VBScript call to a procedure in your project; in this case, it's a procedure named Top.TriggerTitle.

Listing 20.4ùtitles.htx Titles HTML Template File

<HTML>
<HEAD><TITLE>Authors and YTD Sales</TITLE></HEAD>
<BODY>
<BODY BGCOLOR="FFFFFF">
<OBJECT ID="lstBooks" WIDTH=100 HEIGHT=100
CLASSID="CLSID:8BD21D20-EC42-11CE-9E0D-00AA006002F3">
<PARAM NAME="ScrollBars" VALUE="3">
<PARAM NAME="DisplayStyle" VALUE="2">
<PARAM NAME="Size" VALUE="5151;811">
<PARAM NAME="MatchEntry" VALUE="0">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<Script Language="VBScript">
Dim strBuffer
On Error Resume Next
<%begindetail%>
lstBooks.AddItem "<%title%>" & ", " & "<%au_fname%>" & " " & _
"<%au_lname%>" & ", " & "<%Price%>"
<%enddetail%>
<%Query_String%>
<%Remote_User%>
<%request_Method%>
Top.TriggerTitle
</Script>
</BODY>
</HTML>

In Listings 20.2 and 20.4, the script created by the query exists solely for the purpose of loading a ListBox with the results of the query. This forms the basis of the buffer. Once your application is notified that the buffer is loaded, it can proceed to extract the data from the ListBox and perform whatever processing is required.

Using HTML Layout Page Forms

The client end of the project consists of two filesùan HTML file and an .ALX file. The HTML file contains a small amount of standard HTML code and a reference to the .ALX file. The actual Layout Page is contained in the .ALX file. Figure 20.1 shows the HTML file loaded into the ActiveX Control Pad.

FIG. 20.1

Validation.htm is loaded into ActiveX Control Pad.

Listing 20.5 contains three significant sections (apart from the usual HTML tags (such as, <HTML>, <HEAD>, <BODY>, and so on). The <iFRAME> tag defines the nearly-hidden buffer frame, named frmBuffer. As mentioned, it has a width and height of 1, which makes it for all intents and purposes invisible. Because it's in the main HTML file (it's not possible to place it in an HTML Layout Page), it's declared here, and its one-pixel display will appear outside the Layout Page.

Listing 20.5ùvalidation.htm Main HTML form for Validation Example

<HTML>
<HEAD>
<TITLE>Validation Example</TITLE>
</HEAD>
<BODY>
<iFRAME src="" WIDTH=1 HEIGHT=1 NAME="fraBuffer" OnLoad = "Foo">
<FRAME src="" WIDTH=1 HEIGHT=1 NAME="fraBuffer" OnLoad = "Foo">
</iFRAME>
<OBJECT ID="Validation"
CLASSID="CLSID:812AE312-8B8E-11CF-93C8-00AA00C08FDF">
<PARAM NAME="ALXPATH" REF VALUE="validation.alx">
</OBJECT>
<SCRIPT LANGUAGE="VBScript" OnLoad="InitALX">
Sub TriggerPub
Validation.txtTrigger.Text = "pub"
End Sub
Sub TriggerTitle
Validation.txtTrigger.Text = "title"
End Sub
</SCRIPT>
</BODY>
</HTML>

The second section to observe is the declaration beginning with <OBJECT ID="Validation". This is the declaration for the HTML Layout Page, which is named Validation, and is contained in the validation.alx file.

The script is notable because it contains an OnLoad="InitALX" parameter in its declaration. This assures that the script is linked to the HTML Layout Pad. Without that parameter, procedures in the script would not be able to communicate with objects in the Layout Page.

The actual script contains two simple procedures, used to create a link between the buffer frame, and the Layout Page. The first, TriggerPub, is called by the Top.TriggerPub statement in the page returned by the server when requesting a list of publishers.

Sub TriggerPub

Validation.txtTrigger.Text = "pub"

End Sub

The one line of code in the routine sets the value of a hidden TextBox (txtTrigger in the Validation Layout Page) to "pub". Because limitations in the current version of VBScript, this is the easiest way to invoke a routine in a Layout Page. When the contents of the control change, an event is fired, which the script intercepts and uses as notification that the data is loaded in the buffer and ready to go.

Sub TriggerTitle

Validation.txtTrigger.Text = "title"

End Sub

The TriggerTitle procedure is identical to the TriggerPub procedure, except that it sets the value of the TextBox to "title", indicating that the list of book titles is received and ready for processing.

This small HTML page is all that's required to create a foundation for the HTML Layout Page and provide a means to move data in dynamically.

Using ActiveX Controls on Layout Page Forms

The ActiveX Control Pad provides an ideal environment for creating data input and display forms. This project uses a number of visible, and hidden controls. The complete form, with the hidden controls exposed in the Layout editor is shown in Figure 20.2. See Chapters 3, "Introducing the ActiveX Control Pad," and 4, "Creating a Standard HTML Page," for more on this subject.

FIG. 20.2

Here's the complete form with all controls visible.

The controls at the bottom of Figure 20.2 are three ListBoxes and one TextBox.

Because there's quite a bit going on here, it's necessary to hopscotch around in the script in order to adequately describe it. Bear with the discussion, because once you understand the principles involved, you'll be able to create knock-em-dead presentations; the payoff will be well worth the effort.

Debugging Tip

When you're using hidden controls, you may want to make them visible during your initial testing, so that you can see what's going on in them. This can save you hours of frustration, as well as eliminate the need to salt your code with numerous MsgBox statements to report on the status of variables. After testing, be sure to use the Properties window (shown in fig. 20.3) to set the Visible property of your hidden controls to Falseùand, for good measure, move the border of your form to cover them (also shown in fig. 20.3).

FIG. 20.3

Here's the form with the controls hidden.

When you want to edit your Layout code in the ActiveX Control Pad, you have two choices: you can load it into Notepad (via the View Source Code selection available when right-clicking over the form in the Layout Page editor), or you can use the Script Wizard (also available via the right-click pop-up menu).

You may choose to do a little of each. It's very convenient to have the Script Wizard create event and procedure headers and insert method calls (see fig. 20.4). It's also very handy to be able to navigate the object trees, if for no other reason than to have ready access to a visible representation of the structure.

FIG. 20.4

This is the ActiveX Control Pad Script Wizard.

The complete code for the Layout Page in this project is contained in Listing 20.9. Although the Control Pad creates multiple scripts, and the actual placement of procedures within them is immaterial to actual order of execution, the procedures in this file have been moved around by hand into more or less their order of execution, to facilitate explanation in this book. VBScript is an event-driven language, which means that not all things have to happen in any specific order. The code of your real-world applications doesn't have to adhere to any specific order.

Creating a Sample Form

The process of creating a form is fairly straightforward, if not entirely obvious at a glance. The example program starts out in Internet Explorer with an empty form, as shown in Figure 20.5.

FIG. 20.5

Here's the beginning of the example project in Internet Explorer.

The user sees two ListBoxes, two TextBoxes (both TextBoxes are disabled but visible), and two CommandButtons (one of which is disabled). A Label control contains instructions on how to proceed. The ListBox on the left is titled Publishers, and the button beneath it is captioned Get Publishers.

When the user clicks Get Publishers, the application sends a simple command to the server:

Top.Frames(fraBuffer).location = "http://iymalluf.rt66.com/eftproot/pub.idc?"

This statement tells the server to execute the pub.idc query and send the HTML page it creates to the buffer frame (fraBuffer) located on the main HTML page. The Top. prefix specifies that the fraBuffer frame is to be found at the "top" level of the projectùthe main HTML page.

At this point, the program does nothing. But as soon as the page is loaded into the frame, the Top.TriggerPub statement in the returned page is executed, which invokes the TriggerPub procedure in the main HTML page. TriggerPub contains one line of code:

Validation.txtTrigger.Text = "pub"

which sets the contents of the txtTrigger control (on the Layout Page) to "pub". This causes the txtTrigger_Change event (see Listing 20.6) to fire, which brings execution back to the Layout Page.

Listing 20.6 txtTrigger Procedure from validation.alx

Sub txtTrigger_Change() 'Called by kludge routine in main HTML
If Working Then
Exit Sub
End If
Working = True
Select Case txtTrigger.Text
Case "pub"
txtTrigger = ""
Working = False
LoadPublishers 'to provide ersatz load event for frame
Case "title"
txtTrigger = ""
Working = False
LoadTitles 'to provide ersatz load event for frame
Case Else
MsgBox "Illegal call!: " & txtTrigger.Text
txtTrigger = ""
Working = False
End Select
end sub

The txtTrigger_Change event contains a Select Case block.

This block contains three options. One runs the LoadPublishers procedure, if the hidden TextBox contains "pub", one runs LoadTitles if the TextBox contains "title", and the third one is a catch-all that returns an error message via a MessageBox if anything else is in the control.

You'll notice some seemingly extraneous code here. The procedure begins with three lines apparently unrelated to the task at hand:

If Working Then

Exit Sub

End If

The code also contains the lines

txtTrigger = ""

Working = False

in each Case block.

This brings up a problem endemic to all event-driven code. When you change the contents of a TextBox, you trigger the TextBox's Change event. That's not necessarily a bad thing; in fact, it's the very "trick" we used to get here in the first place.

However, because we want to be able to call this procedure more than once, we have to clear the contents of the TextBox after we use them. But, if we try to arrive here with "title" twice in a row, we'll be replacing "title" with "title", which means there's no change, and hence, no Change event!

The solution seems obvious: clear the contents of the TextBox as soon as we've used them. But there's a catch: when you clear the contents of the TextBox, you're changing it. This, in turn, fires off another Change event. Because the event occurs while you're still in the Change event, you invoke what's called cascading events, which is generally considered a very bad thing. In fact, it can quickly crash your program by consuming all stack space, as it repeatedly calls itself.

In this example, the event would not cascade too far. In fact, it would only cascade once; the moment it double-fired, the Select Case block would fall through to the third test and report an error, because the empty TextBox does not match either of the two valid options.

The solution is an old trick: you use a variable (in this case, one called "Working") to track whether you're in the procedure the first time or are in a cascaded invocation. When the procedure is first run, it checks whether the value of "Working" is True. If it is, the procedure recognizes that it is already executing, and it immediately exits. If it's not, it proceeds. After the TextBox is set to "" to empty it, "Working" is restored to False, to allow the procedure run the next time it's invoked.

Because VBScript does not at this time provide a means to create static variables (a static variable, legal in Visual Basic, will retain its value between successive invocations of a procedure) it's necessary to declare "Working" outside of any procedure, to give it global scope.

Returning to the development of the example program, the LoadPublishers procedure does as its name impliesùit loads the list of publishers from the ListBox contained in the invisible frame. The procedure is shown in Listing 20.7.

Listing 20.7 LoadPublishers Procedure from validation.alx

Sub LoadPublishers
For C = 0 to Top.Frames(fraBuffer).lstPublishers.ListCount -1
WkTxt = Top.Frames(fraBuffer).lstPublishers.List(C)
StartPos = Instr(WkTxt, ";")ù1
lstPub.AddItem Left(WkTxt, StartPos)
StartPos = StartPos + 2
EndPos = Instr(StartPos, WkTxt, ";")
lstPubID.AddItem Mid(WkTxt, StartPos, EndPosùStartPos)
StartPos = EndPos + 1
EndPos = Instr(StartPos, WkTxt, ";")
lstHighPrice.AddItem Mid(WkTxt, StartPos, EndPosùStartPos)
StartPos = EndPos + 1
EndPos = Len(WkTxt)
lstLowPrice.AddItem Mid(WkTxt, StartPos, EndPosùStartPos)
Next
lstPub.ListIndex = 0 'Select first item.
PubID = lstPubID.List(0) 'publisher ID
PubMin = CDbl(lstLowPrice.List(0)) 'publisher minumum
PubMax = CDbl(lstHighPrice.List(0)) 'publisher maximum
lblAbsMin = "(" & PubMin & ")"
lblAbsMax = "(" & PubMax & ")"
txtMin.Enabled=True 'Allow user to proceed to set Min
txtMax.Enabled=True 'Allow user to proceed to set Max
cmdTitles.Enabled=True 'Allow user to proceed to get Titles
end sub

The LoadPublishers procedure takes each entry in the Top.Frames(fraBuffer).lstPublishers ListBox using the following line:

For C = 0 to Top.Frames(fraBuffer).lstPublishers.ListCount -1

to count through them. Because the first entry in a ListBox is 0, the count starts at 0 and continues to one less than the value in the ListBox's ListCount property. (ListCount contains the number of items in the list, not the number of the highest item.)

The items in the ListBox contain four pieces of data: the publisher's name, the publisher's ID number, the price of the publisher's most expensive book, and the price of its least expensive book. The pieces of data are separated by semicolons.

This data is separated into four ListBoxes (the large one on the left of the form and the three hidden ones on the bottom). The publisher's name goes into the visible ListBox, and the other three bits of information are used by the program to validate entry and construct queries.

To separate this data, it's first put into a string named "WkTxt" (which stands for work text):

WkTxt = Top.Frames(fraBuffer).lstPublishers.List(C)

The .List(C) property returns the value of the item at the position represented by C, which is the variable created by the For-Next loop.

StartPos = Instr(WkTxt, ";")ù1

lstPub.AddItem Left(WkTxt, StartPos)

"StartPos" a numeric variable, set to contain a value representing a position one character before the position of the first semicolon in the string, using the Instr function, which searches one string for a match with a smaller string. Then "lstPub" (the visible ListBox) is set to the "Left" part of the string (up to but not including the semicolon).

StartPos = StartPos + 2

EndPos = Instr(StartPos, WkTxt, ";")

lstPubID.AddItem Mid(WkTxt, StartPos, EndPosùStartPos)

The process is repeated above for the second part of the string. In this case, it's modified, since it's not at the start of the string, therefore, the Left function can't be used. Instead, the Mid function is used to extract characters from within the string. The first line sets StartPos to point one character after the semicolon. The second line creates a new variable, EndPos, which points to the second semicolon. (Instr is invoked here using the optional first parameter, which tells it where in the string to start searching.)

Then, the Mid function returns the second item by fetching the characters between the two semicolons. The first parameter in the Mid function ("WkTxt") is the string containing all the bits of data in the item. The second ("StartPos") tells Mid where to start copying characters, and the third parameter contains an expression; the result of "EndPos - StartPos" contains the number of characters to copy.

The characters are copied into the invisible lstPubID ListBox, for use later on with the query to the server.

StartPos = EndPos + 1

EndPos = Instr(StartPos, WkTxt, ";")

lstHighPrice.AddItem Mid(WkTxt, StartPos, EndPosùStartPos)

The third bit of data is extracted using the identical process as that used for the second, except this time, the retrieved information is loaded into the lstHighPrice invisible ListBox.

StartPos = EndPos + 1

EndPos = Len(WkTxt)

lstLowPrice.AddItem Mid(WkTxt, StartPos, EndPosùStartPos)

Finally, the last part of the item is retrieved. This time, EndPos is simply set to the length of "WkTxt" by use of the Len string handling function. Because there's no closing semicolon, the length of the string marks the last character that's required here.

This process is repeated for each item in the ListBox contained in the buffer frame. Then, a few global variables are set:

PubID = lstPubID.List(0) 'publisher ID

PubMin = CDbl(lstLowPrice.List(0)) 'publisher minumum

PubMax = CDbl(lstHighPrice.List(0)) 'publisher maximum

PubID, PubMin, and PubMax are described in the comments at the end of each line. PubID is used for the queries of book titles, and the PubMin and PubMax variables are used in data validation.

lblAbsMin = "(" & PubMin & ")"

lblAbsMax = "(" & PubMax & ")"

Two labels on the form are set to display the absolute minimum and maximum prices of all books the current publisher carries. (The "current publisher" is whichever one is highlighted in the Publishers ListBox.)

txtMin.Enabled=True 'Allow user to proceed to set Min

txtMax.Enabled=True 'Allow user to proceed to set Max

cmdTitles.Enabled=True 'Allow user to proceed to get Titles

Finally, the three disabled controls are enabled, allowing the user to proceed. Now the program waits for something to happen.

At this point, the example will appear as shown in Figure 20.6.

FIG. 20.6

Here's the example project after getting publisher data.

Using Data Validation

You can't always prevent bad data by disabling controls. In this example, the user is expected to select a publisher, then enter a desired range of prices, and submit a query. You can prevent the user from entering prices or sending a query before publisher data is loaded fairly easilyùjust disable the relevant controls. But what of the situation after the data is loaded? What happens if the user enters a low price that is lower than the publisher's least expensive title or a high price that is higher than the most expensive?

In this case, you could rely on the server to trap the error. But it could take a long time, and it might not even trap it! One of the features of VBScript is the ability to validate data locally, before sending it to the server.

In this example, the values entered by the user are validated using a simple set of rules: the user has to enter both numbers; the lowest can't be lower than the least expensive book, the highest can't be higher than the most expensive, and the highest can't be lower than the lowest.

If the user clicks Get Titles but hasn't entered any numbers, the application displays an error message as shown in Figure 20.7

FIG. 20.7

In this error example, no minimum value was specified.

If the minimum is lower than the least expensive book, the message in Figure 20.8 is shown.

FIG. 20.8

In this error example, the minimum value specified was too low.

Because the program shows these MessageBoxes instead of simply passing bad data on to the server, the user receives immediate feedback and can correct the situation without waiting for the server to process and pass back its data. This kind of entry-error-prevention also makes it easier for the program to parse incoming data, as it's not necessary to test for these errors when processing query results.

When the user clicks Get Titles, the code in the ValidateQuery procedure (shown in Listing 20.8) executes and performs the tests necessary to trap for these errors.

Listing 20.8 ValidateQuery Procedure from validation.alx

Sub ValidateQuery()
Min = Trim(txtMin.Text) 'Store values to variables
Max = Trim(txtMax.Text)
If Min = "" Then
MsgBox "Must enter a Minimum value!"
Exit Sub
End If
If Max = "" Then
MsgBox "Must enter a Maximum value!"
Exit Sub
End If
Min = CDbl(Min) 'Convert variables to numeric
Max = CDbl(Max)
ErStr = "" 'initialize to empty string
If Min > 0 then
If Max > Min Then
If Min >= PubMin Then
If Max <= PubMax Then
SubmitTitles
Else
ErStr = "Max is greater than highest price available!"
End If
Else
ErStr = "Min is less than lowest price available!"
End If
Else
ErStr = "Max must be greater than Min!"
End If
Else
ErStr = "Min must be greater than 0!"
End If
If ErStr > "" then MsgBox ErStr
end sub

The code in Listing 20.8 is fairly straightforward and fairly self-documenting. It first checks to make sure the user has entered values; then it tests for all possible error conditions, using a series of nested If-Then blocks. By properly indenting the If-Then tests, you can tell at a glance where each block starts and ends.

After all data is accepted as valid, the procedure passes control to the SubmitTitles procedure, which constructs a string containing the query and sends it on its way to the server (see fig. 20.9).

FIG. 20.9

Titles are returned when data is validated.

Handling Data Transactions

The SubmitTitles procedure is elegant in its simplicity. All the data having been accepted and validated prior to arrival here, it's relatively trivial to combine it into a query string, and send it to the server. The string consists of the URL of the server, which is comprised of the machine address ("http://iymalluf.rt66.com"), the path containing the query file ("/eftproot/"), the name of the query file ("titles.idc"), a question-mark, which indicates that it's a query to execute rather than a file to fetch, and the query itself: "pubID=" & PubID & "&HighPrice=" & Max & "&LowPrice=" & Min". The query consists of names in the format required by the server ("pubID=", "&HighPrice=", "&LowPrice=") concatenated with the names of the VBScript variables containing the actual data:

Sub SubmitTitles() 'query server

lstTitles.Clear

Top.Frames(fraBuffer).location="http://iymalluf.rt66.com/eftproot/titles.idc?

[ic: ccc]pubID=" & PubID & "&HighPrice=" & Max & "&LowPrice=" & Min

end sub

After this query is sent, the application does nothing until notified that the result file is waiting in the buffer frame, exactly as with the Publishers query. When the result file is received, LoadTitles is executed, and the Titles ListBox is populated with the results. Because the list of book titles doesn't need to be separated into discrete elements, placing it into the ListBox is simple:

Sub LoadTitles

For C = 0 to Top.Frames(fraBuffer).lstBooks.ListCount -1

lstTitles.AddItem Top.Frames(fraBuffer).lstBooks.List(C)

Next

end sub

The LoadTitles procedure counts through the list of items and adds them, one at a time, to the lstTitles ListBox.

Understanding the Layout Page Source Listing

Listing 20.9 contains the entire source for the HTML Layout Page used in this example. The section after the final script contains declarations for the controls used in the project.

Listing 20.9ùvalidation.alx Layout Page Code for Validation Example

<SCRIPT LANGUAGE="VBScript">
<!--
Dim PubID, PubMin, PubMax, Min, Max, Working
Sub txtTrigger_Change() 'Called by kludge routine in main HTML
If Working Then
Exit Sub
End If
Working = True
Select Case txtTrigger.Text
Case "pub"
txtTrigger = ""
Working = False
LoadPublishers 'to provide ersatz load event for frame
Case "title"
txtTrigger = ""
Working = False
LoadTitles 'to provide ersatz load event for frame
Case Else
MsgBox "Illegal call!: " & txtTrigger.Text
txtTrigger = ""
Working = False
End Select
end sub
Sub ClearPubLists
lstPub.Clear
lstPubID.Clear
lstLowPrice.Clear
lstHighPrice.Clear
End Sub
-->
</SCRIPT>
<SCRIPT LANGUAGE="VBScript">
<!--
Sub cmdPub_Click()
ClearPubLists
lstTitles.Clear
REM query server, load hidden listbox into lstPub
Top.Frames(fraBuffer).location="http://iymalluf.rt66.com/eftproot/
[ic: ccc]pub.idc?"
end sub
Sub LoadPublishers
For C = 0 to Top.Frames(fraBuffer).lstPublishers.ListCount -1
WkTxt = Top.Frames(fraBuffer).lstPublishers.List(C)
StartPos = Instr(WkTxt, ";")ù1
lstPub.AddItem Left(WkTxt, StartPos)
StartPos = StartPos + 2
EndPos = Instr(StartPos, WkTxt, ";")
lstPubID.AddItem Mid(WkTxt, StartPos, EndPosùStartPos)
StartPos = EndPos + 1
EndPos = Instr(StartPos, WkTxt, ";")
lstHighPrice.AddItem Mid(WkTxt, StartPos, EndPosùStartPos)
StartPos = EndPos + 1
EndPos = Len(WkTxt)
lstLowPrice.AddItem Mid(WkTxt, StartPos, EndPosùStartPos)
Next
lstPub.ListIndex = 0 'Select first item.
PubID = lstPubID.List(0) 'publisher ID
PubMin = CDbl(lstLowPrice.List(0)) 'publisher minumum
PubMax = CDbl(lstHighPrice.List(0)) 'publisher maximum
lblAbsMin = "(" & PubMin & ")"
lblAbsMax = "(" & PubMax & ")"
txtMin.Enabled=True 'Allow user to proceed to set Min
txtMax.Enabled=True 'Allow user to proceed to set Max
cmdTitles.Enabled=True 'Allow user to proceed to get Titles
end sub
-->
</SCRIPT>
<SCRIPT LANGUAGE="VBScript">
<!--
Sub cmdTitles_Click()
ValidateQuery
end sub
Sub ValidateQuery()
Min = Trim(txtMin.Text) 'Value) 'Store values to variables
Max = Trim(txtMax.Text) 'Value)
If Min = "" Then
MsgBox "Must enter a Minimum value!"
Exit Sub
End If
If Max = "" Then
MsgBox "Must enter a Maximum value!"
Exit Sub
End If
Min = CDbl(Min) 'Convert variables to numeric
Max = CDbl(Max)
ErStr = "" 'initialize to empty string
If Min > 0 then
If Max > Min Then
If Min >= PubMin Then
If Max <= PubMax Then
SubmitTitles
Else
ErStr = "Max is greater than highest price available!"
End If
Else
ErStr = "Min is less than lowest price available!"
End If
Else
ErStr = "Max must be greater than Min!"
End If
Else
ErStr = "Min must be greater than 0!"
End If
If ErStr > "" then MsgBox ErStr
end sub
Sub SubmitTitles() 'query server
lstTitles.Clear
Top.Frames(fraBuffer).location="http://iymalluf.rt66.com/eftproot/titles.idc?
[ic: ccc]pubID=" & PubID & "&HighPrice=" & Max & "&LowPrice=" & Min
end sub
Sub LoadTitles
For C = 0 to Top.Frames(fraBuffer).lstBooks.ListCount -1
lstTitles.AddItem Top.Frames(fraBuffer).lstBooks.List(C)
Next
end sub
-->
</SCRIPT>
<SCRIPT LANGUAGE="VBScript">
<!--
Sub lstPub_Click()
PubID = lstPubID.list(lstPub.ListIndex) 'publisher ID
PubMin = CDbl(lstLowPrice.list(lstPub.ListIndex)) 'publisher minumum
PubMax = CDbl(lstHighPrice.list(lstPub.ListIndex)) 'publisher maximum
lblAbsMin = "(" & PubMin & ")"
lblAbsMax = "(" & PubMax & ")"
End Sub
-->
</SCRIPT>
<DIV ID="hlValid" STYLE="LAYOUT:FIXED;WIDTH:519pt;HEIGHT:290pt;">
<OBJECT ID="cmdPub"
CLASSID="CLSID:D7053240-CE69-11CD-A777-00DD01143C57"
[ic: ccc]STYLE="TOP:206pt;LEFT:33pt;WIDTH:66pt;HEIGHT:25pt;TABINDEX:0;ZINDEX:0;">
<PARAM NAME="Caption" VALUE="Get Publishers">
<PARAM NAME="Size" VALUE="2328;882">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="ParagraphAlign" VALUE="3">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="cmdTitles"
CLASSID="CLSID:D7053240-CE69-11CD-A777-00DD01143C57"
[ic: ccc]STYLE="TOP:206pt;LEFT:385pt;WIDTH:66pt;HEIGHT:25pt;TABINDEX:1;ZINDEX:1;">
<PARAM NAME="VariousPropertyBits" VALUE="25">
<PARAM NAME="Caption" VALUE="Get Titles">
<PARAM NAME="Size" VALUE="2328;882">
<PARAM NAME="FontEffects" VALUE="1073750016">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="ParagraphAlign" VALUE="3">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lblPublishers"
CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0"
[ic: ccc] STYLE="TOP:8pt;LEFT:34pt;WIDTH:74pt;HEIGHT:17pt;ZINDEX:2;">
<PARAM NAME="Caption" VALUE="Publishers">
<PARAM NAME="PicturePosition" VALUE="393216">
<PARAM NAME="Size" VALUE="2611;600">
<PARAM NAME="BorderStyle" VALUE="1">
<PARAM NAME="FontEffects" VALUE="1073741825">
<PARAM NAME="FontHeight" VALUE="240">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="ParagraphAlign" VALUE="3">
<PARAM NAME="FontWeight" VALUE="700">
</OBJECT>
<OBJECT ID="lblTitles"
CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0"
[ic: ccc]STYLE="TOP:8pt;LEFT:296pt;WIDTH:50pt;HEIGHT:17pt;ZINDEX:3;">
<PARAM NAME="Caption" VALUE="Titles">
<PARAM NAME="Size" VALUE="1764;600">
<PARAM NAME="BorderStyle" VALUE="1">
<PARAM NAME="FontEffects" VALUE="1073741825">
<PARAM NAME="FontHeight" VALUE="240">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="ParagraphAlign" VALUE="3">
<PARAM NAME="FontWeight" VALUE="700">
</OBJECT>
<OBJECT ID="txtMin"
CLASSID="CLSID:8BD21D10-EC42-11CE-9E0D-00AA006002F3"
[ic: ccc]STYLE="TOP:198pt;LEFT:319pt;WIDTH:41pt;HEIGHT:16pt;TABINDEX:5;ZINDEX:4;">
<PARAM NAME="VariousPropertyBits" VALUE="746604569">
<PARAM NAME="Size" VALUE="1446;564">
<PARAM NAME="FontEffects" VALUE="1073750016">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="txtMax"
CLASSID="CLSID:8BD21D10-EC42-11CE-9E0D-00AA006002F3"
[ic: ccc]STYLE="TOP:223pt;LEFT:319pt;WIDTH:41pt;HEIGHT:16pt;TABINDEX:6;ZINDEX:5;">
<PARAM NAME="VariousPropertyBits" VALUE="746604569">
<PARAM NAME="Size" VALUE="1446;564">
<PARAM NAME="FontEffects" VALUE="1073750016">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lblMin"
CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0"
[ic: ccc]STYLE="TOP:198pt;LEFT:229pt;WIDTH:41pt;HEIGHT:8pt;ZINDEX:6;">
<PARAM NAME="Caption" VALUE="Min. Price">
<PARAM NAME="Size" VALUE="1446;282">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lblMax"
CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0"
[ic: ccc]STYLE="TOP:223pt;LEFT:229pt;WIDTH:41pt;HEIGHT:8pt;ZINDEX:7;">
<PARAM NAME="Caption" VALUE="Max. Price">
<PARAM NAME="Size" VALUE="1446;282">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lblInstructions"
CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0"
[ic: ccc]STYLE="TOP:256pt;LEFT:121pt;WIDTH:281pt;HEIGHT:25pt;ZINDEX:8;">
<PARAM NAME="Caption" VALUE="Click 'Get Publishers' to
[ic: ccc] retrieve list of publishers. Then, enter desired range
[ic: ccc] of prices, and click 'Get Titles' to retrieve list of
[ic: ccc] titles within range.">
<PARAM NAME="Size" VALUE="9913;882">
<PARAM NAME="SpecialEffect" VALUE="2">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="txtTrigger"
CLASSID="CLSID:8BD21D10-EC42-11CE-9E0D-00AA006002F3"
[ic: ccc] STYLE="TOP:322pt;LEFT:289pt;WIDTH:40pt;HEIGHT:17pt;TABINDEX:10;
[ic: ccc]DISPLAY:NONEZINDEX:9;">
<PARAM NAME="VariousPropertyBits" VALUE="746604571">
<PARAM NAME="Size" VALUE="1411;600">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lblAbsMin"
CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0"
[ic: ccc] STYLE="TOP:198pt;LEFT:278pt;WIDTH:26pt;HEIGHT:17pt;ZINDEX:10;">
<PARAM NAME="Size" VALUE="917;600">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lblAbsMax"
CLASSID="CLSID:978C9E23-D4B0-11CE-BF2D-00AA003F40D0"
[ic: ccc] STYLE="TOP:223pt;LEFT:278pt;WIDTH:26pt;HEIGHT:17pt;ZINDEX:11;">
<PARAM NAME="Size" VALUE="917;600">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lstTitles"
CLASSID="CLSID:8BD21D20-EC42-11CE-9E0D-00AA006002F3"
[ic: ccc] STYLE="TOP:33pt;LEFT:150pt;WIDTH:362pt;HEIGHT:155pt;TABINDEX:13;
[ic: ccc]ZINDEX:12;">
<PARAM NAME="VariousPropertyBits" VALUE="746586139">
<PARAM NAME="ScrollBars" VALUE="3">
<PARAM NAME="DisplayStyle" VALUE="2">
<PARAM NAME="Size" VALUE="12771;5468">
<PARAM NAME="MatchEntry" VALUE="0">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lstPubID"
CLASSID="CLSID:8BD21D20-EC42-11CE-9E0D-00AA006002F3"
[ic: ccc] STYLE="TOP:305pt;LEFT:83pt;WIDTH:57pt;HEIGHT:42pt;TABINDEX:16;
[ic: ccc]DISPLAY:NONEZINDEX:13;">
<PARAM NAME="ScrollBars" VALUE="3">
<PARAM NAME="DisplayStyle" VALUE="2">
<PARAM NAME="Size" VALUE="2011;1482">
<PARAM NAME="MatchEntry" VALUE="0">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lstPub"
CLASSID="CLSID:8BD21D20-EC42-11CE-9E0D-00AA006002F3"
[ic: ccc] STYLE="TOP:33pt;LEFT:0pt;WIDTH:146pt;HEIGHT:155pt;TABINDEX:2;
[ic: ccc]ZINDEX:14;">
<PARAM NAME="VariousPropertyBits" VALUE="746586139">
<PARAM NAME="ScrollBars" VALUE="3">
<PARAM NAME="DisplayStyle" VALUE="2">
<PARAM NAME="Size" VALUE="5151;5468">
<PARAM NAME="MatchEntry" VALUE="0">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lstHighPrice"
CLASSID="CLSID:8BD21D20-EC42-11CE-9E0D-00AA006002F3"
[ic: ccc] STYLE="TOP:314pt;LEFT:157pt;WIDTH:57pt;HEIGHT:36pt;TABINDEX:14;DISPLAY:
[ic: ccc]NONEZINDEX:15;">
<PARAM NAME="ScrollBars" VALUE="3">
<PARAM NAME="DisplayStyle" VALUE="2">
<PARAM NAME="Size" VALUE="2011;1270">
<PARAM NAME="MatchEntry" VALUE="0">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
<OBJECT ID="lstLowPrice"
CLASSID="CLSID:8BD21D20-EC42-11CE-9E0D-00AA006002F3"
[ic: ccc] STYLE="TOP:314pt;LEFT:223pt;WIDTH:57pt;HEIGHT:35pt;TABINDEX:15;
[ic: ccc]DISPLAY:NONEZINDEX:16;">
<PARAM NAME="ScrollBars" VALUE="3">
<PARAM NAME="DisplayStyle" VALUE="2">
<PARAM NAME="Size" VALUE="2011;1235">
<PARAM NAME="MatchEntry" VALUE="0">
<PARAM NAME="FontCharSet" VALUE="0">
<PARAM NAME="FontPitchAndFamily" VALUE="2">
<PARAM NAME="FontWeight" VALUE="0">
</OBJECT>
</DIV>

From Here...

The exciting possibilities shown in this chapter require a working knowledge of the HTML Control Pad, HTML Layout Pages, and the VBScript language. To a lesser extent, an understanding of HTML is important.


| Previous Chapter | Next Chapter |

| Search | Table of Contents | Book Home Page | Buy This Book |

| Que Home Page | Digital Bookshelf | Disclaimer |


To order books from QUE, call us at 800-716-0044 or 317-361-5400.

For comments or technical support for our books and software, select Talk to Us.

© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.